
import sys
import thread
import threading
import traceback

from coffeetalk.protocol import ioelement, ioml, requests, iotypes
from coffeetalk.utilities import iologger, objectmaker


class _IODispatcher(object):
    
    def __init__(self,ioclient,log=iologger.IONullLogger()):
        
        self.ioclient = ioclient
        self.log = log
        
        self._objectmaker = objectmaker.ObjectMaker()

        self.requests = {}
        self.requestslock = thread.allocate_lock()
        
        self.subscribers = {}
        self.subscriberlock = thread.allocate_lock()
        
        self.anchorsubscribers = {}
        self.anchorsubscriberlock = thread.allocate_lock()
        
        self.monitors = {}
        self.monitorslock = thread.allocate_lock()
        
        self.partialresponses = {}
        self.partialresponseslock = thread.allocate_lock()
            
    def add_request(self,request):
        self.requestslock.acquire_lock()
        if self.requests.has_key(request.requestid):
            self.log.log_error(iologger.WARNING,'0.0.0.0','""','""',iologger.current_function(),'InternalError','requestid already exists')
        self.requests[request.requestid] = request
        self.requestslock.release()
    
    def subscription_exists(self,path):
        exists = False
        self.subscriberlock.acquire()
        if self.subscribers.has_key(path):
            exists = True
        self.subscriberlock.release()
        return exists
    
    def monitor_exists(self,path,monitortype):
        exists = False
        self.monitorslock.acquire()
        if self.monitors.has_key(path):
            if self.monitors[path].has_key(monitortype):
                exists = True
        self.monitorslock.release()
        return exists
      
    def dispatch_message(self,message):

        iomessage = ioml.IOML(iomlstring=message)
        if hasattr(self,'_handle_' + iomessage.headelement.name):
            self.__getattribute__('_handle_' + iomessage.headelement.name)(iomessage)
        else:
            self._handle_response(iomessage)
    
    def _handle_response(self,iomessage):
        
        self.requestslock.acquire()
        if self.requests.has_key(iomessage.headelement.name):
            request = self.requests[iomessage.headelement.name]
            self.requestslock.release()
            deleterequest = True
            
            env = None
            if iomessage.headelement.childelements.has_key('ENV'):
                env = iotypes.Environment('','','','','',element=iomessage.headelement.childelements['ENV'])
            else:
                env = iotypes.Environment('0.0.0.0','(unknown)','(unknown)','0','0')
                
            if iomessage.headelement.attribs.has_key('type'):
                responsetype = iomessage.headelement.attribs['type']
                if responsetype == 'responses.OK':
                    self._call_on_success_handler(request,env)
                    if isinstance(request,requests.Subscribe):
                        self._add_subscriber(request,iomessage,env)
                    elif isinstance(request,requests.Unsubscribe):
                        self._remove_subscriber(request,env)
                    elif isinstance(request,requests.AddMonitor):
                        self._add_monitor(request, iomessage, env)
                    elif isinstance(request,requests.DeleteMonitor):
                        self._remove_monitor(request, env)
                    elif isinstance(request,requests.Get):
                        self._call_on_get_handler(request,iomessage.bodyelement,env)
                    elif isinstance(request,requests.Add):
                        if request.on_add is not None:
                            newobj = iomessage.bodyelement
                            if not request.rawelement:
                                newobj = self._objectmaker.make_object(newobj)
                            self._call_on_add_handler(request, env, newobj)
                elif responsetype == 'responses.Accepted':
                    deleterequest = False
                elif responsetype == 'responses.PartialContent':
                    if request.on_partial is not None:
                        self._call_on_partial_handler(request,iomessage.bodyelement,env,)
                    if request.on_get is not None:
                        self._add_partialcontent(request,iomessage.bodyelement,env)
                    deleterequest = False
                else:
                    err = responsetype
                    if 'err' in iomessage.headelement.attribs:
                        err = iomessage.headelement.attribs['err']
                    self._call_on_error_handler(request,err,env)
                
                if deleterequest:
                    self.requestslock.acquire()
                    del self.requests[iomessage.headelement.name]
                    self.requestslock.release()
            else:
                self.log.log_error(iologger.WARNING,'0.0.0.0','""','""',iologger.current_function(),'InternalError','response type is invalid')
            
        else:
            self.requestslock.release()
            self.log.log_error(iologger.WARNING,'0.0.0.0','""','""',iologger.current_function(),'InternalError','requestid does not exist')
    
    def _handle_Update(self,iomessage):
        
        self.subscriberlock.acquire_lock()
        try:
            if self.subscribers.has_key(iomessage.headelement.attribs['path']):
                
                env = iotypes.Environment('','','','','',element=iomessage.headelement.childelements['ENV'])
                if iomessage.headelement.attribs['type'] == 'updates.Add':
                    newobj = self._objectmaker.make_object(iomessage.bodyelement)
                    for v in self.subscribers[iomessage.headelement.attribs['path']]:
                        if v.rawelement == False:
                            self._call_on_add_handler(v, env, newobj)
                        else:
                            self._call_on_add_handler(v,env,iomessage.bodyelement)
                elif iomessage.headelement.attribs['type'] == 'updates.AnchorAdd':
                    print 'Anchor add!!!!'
                elif iomessage.headelement.attribs['type'] == 'updates.Delete':
                    for v in self.subscribers[iomessage.headelement.attribs['path']]:
                        self._call_on_delete_handler(v, env, iomessage.headelement.attribs['key'])
                elif iomessage.headelement.attribs['type'] == 'updates.AnchorDelete':
                    print 'Anchor Delete!!!!'
        except:
            traceback.print_exc()
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=1),'Exception',str(type) + ':' + str(value))
        finally:
            self.subscriberlock.release()
        
    def _handle_Monitor(self,iomessage):
        self.monitorslock.acquire()
        try:
            path = iomessage.headelement.attribs['path']
            monitortype = iomessage.headelement.attribs['type']
            if path in self.monitors and monitortype in self.monitors[path]:
                env = iotypes.Environment('','','','','',element=iomessage.headelement.childelements['ENV'])
                if 'key' in iomessage.headelement.attribs:
                    for mon in self.monitors[path][monitortype]:
                        self._call_on_add_handler(mon, env, iotypes.String(name=monitortype,value=iomessage.headelement.attribs['key']))
                else:
                    # Body element should contain something. Maybe
                    if iomessage.bodyelement is not None:
                        print 'BodyElement: ' + str(iomessage.bodyelement)
                        newobj = self._objectmaker.make_object(iomessage.bodyelement)
                        for mon in self.monitors[path][monitortype]:
                            self._call_on_add_handler(mon, env, newobj)
                    else:
                        if monitortype == 'Get':
                            newobj = iotypes.String(name=monitortype,value=env.username)
                            for mon in self.monitors[path][monitortype]:
                                self._call_on_add_handler(mon, env, newobj)
                        else:
                            print '_handle_Monitor: bodyelement is None when it should not be'
        except:
            traceback.print_exc()
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=0),'Exception',str(type) + ':' + str(value))
        self.monitorslock.release()
    
    def _add_monitor(self,monrequest,monresponse,env):
        self.monitorslock.acquire()
        if not monrequest.path in self.monitors:
            self.monitors[monrequest.path] = {}
        if not monrequest.monitortype in self.monitors[monrequest.path]:
            self.monitors[monrequest.path][monrequest.monitortype] = []
        
        self.monitors[monrequest.path][monrequest.monitortype].append(monrequest)
        
        if monresponse.bodyelement is not None:
            rootnode = monresponse.bodyelement
            for child in rootnode.itervalues():
                if monrequest.rawelement:
                    self._call_on_add_handler(monrequest, env, child)
                else:
                    newobj = self._objectmaker.make_object(child)
                    self._call_on_add_handler(monrequest, env, newobj)
        self.monitorslock.release()
        
    def _remove_monitor(self,delmonrequest,env):
        
        self.monitorslock.acquire()
        if delmonrequest.path in self.monitors and delmonrequest.monitortype in self.monitors[delmonrequest.path]:
            if len(self.monitors[delmonrequest.path][delmonrequest.monitortype]) == 1:
                del self.monitors[delmonrequest.path][delmonrequest.monitortype]
            else:
                mon = None
                for v in self.monitors[delmonrequest.path][delmonrequest.monitortype]:
                    if id(v.on_monitor) == id(delmonrequest.on_monitor):
                        mon = v
                
                if mon is None:
                    self._call_on_error_handler(delmonrequest, env, 'Monitor not found')
                    return
                if mon in self.monitors[delmonrequest.path][delmonrequest.monitortype]:
                    self.monitors[delmonrequest.path][delmonrequest.monitortype].remove(mon)
                    self._call_on_success_handler(delmonrequest, env)
        self.monitorslock.release()
       
    def _add_subscriber(self,subrequest,subresponse,env):
        
        self.subscriberlock.acquire()
        
        if not self.subscribers.has_key(subrequest.path):
            self.subscribers[subrequest.path] = []
        self.subscribers[subrequest.path].append(subrequest)
        
        if subresponse.bodyelement is not None:
            rootnode = subresponse.bodyelement
            for child in rootnode.itervalues():
                if subrequest.rawelement:
                    self._call_on_add_handler(subrequest, env, child)
                else:
                    newobj = self._objectmaker.make_object(child)
                    self._call_on_add_handler(subrequest, env, newobj)
        self.subscriberlock.release()
    
    def _add_anchor_subscriber(self,subrequest,subresponse,env):
        
        self.anchorsubscriberlock.acquire()
        
        if not self.anchorsubscribers.has_key(subrequest.path):
            self.anchorsubscribers[subrequest.path] = []
        self.anchorsubscribers[subrequest.path].append(subrequest)
        
        if subresponse.bodyelement is not None:
            rootnode = subresponse.bodyelement
            for child in rootnode.itervalues():
                if subrequest.rawelement:
                    self._call_on_add_handler(subrequest, env, child)
                else:
                    newobj = self._objectmaker.make_object(child)
                    self._call_on_add_handler(subrequest, env, newobj)
        self.anchorsubscriberlock.release()
        
    def _remove_subscriber(self,unsubrequest,env):
        
        self.subscriberlock.acquire()
        
        if self.subscribers.has_key(unsubrequest.path):
            if len(self.subscribers[unsubrequest.path]) == 1:
                del self.subscribers[unsubrequest.path]
            else:
                # Order of removal: on_add, on_delete
                sub = None
                for k,v in self.subscribers[unsubrequest.path]:
                    if id(v.on_add) == id(unsubrequest.on_add):
                        sub = k
                if sub is None:
                    for k,v in self.subscribers[unsubrequest.path]:
                        if id(v.on_delete) == id(unsubrequest.on_delete):
                            sub = v
                if sub is None:
                    self._call_on_error_handler(unsubrequest, env, 'Subscription not found')
                    return
                
                if sub in self.subscribers[unsubrequest.path]:
                    self.subscribers[unsubrequest.path].remove(sub)
                    self._call_on_success_handler(unsubrequest, self.env)
        
        self.subscriberlock.release()
    
    def _remove_anchor_subscriber(self,unsubrequest,env):
        
        self.anchorsubscriberlock.acquire()
        
        if self.anchorsubscribers.has_key(unsubrequest.path):
            if len(self.anchorsubscribers[unsubrequest.path]) == 1:
                del self.anchorsubscribers[unsubrequest.path]
            else:
                # Order of removal: on_add, on_delete
                sub = None
                for k,v in self.anchorsubscribers[unsubrequest.path]:
                    if id(v.on_add) == id(unsubrequest.on_add):
                        sub = k
                if sub is None:
                    for k,v in self.anchorsubscribers[unsubrequest.path]:
                        if id(v.on_delete) == id(unsubrequest.on_delete):
                            sub = v
                if sub is None:
                    self._call_on_error_handler(unsubrequest, env, 'Subscription not found')
                    return
                
                if sub in self.anchorsubscribers[unsubrequest.path]:
                    self.anchorsubscribers[unsubrequest.path].remove(sub)
                    self._call_on_success_handler(unsubrequest, self.env)
                    
        self.anchorsubscriberlock.release()
        
    def _add_partialcontent(self,request,element,env):
        if request.on_get is None: return        
        
        self.partialresponseslock.acquire()
        
        requestid = request.requestid
        path = request.path
        if path[-1:] == '/':
            path = path[:-1]
        parentnode = path.rsplit('/',1)[1]
        if not self.partialresponses.has_key(requestid):
            self.partialresponses[requestid] = ioelement.IOElement(parentnode,{})
        
        if element is not None and element.name == parentnode:
            for child in element.childelements.itervalues():
                self.partialresponses[requestid].add(child)
        
        self.partialresponseslock.release()
   
    def _call_on_success_handler(self,request,env):
        
        if request.on_success is None: return
        try:
            request.on_success(env,request.path,**request.kwargs)
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=0),'Exception',str(type) + ':' + str(value))
    
    def _call_on_error_handler(self,request,err,env):
        
        if request.on_error is None: return
        try:
            request.on_error(env,request.path,request,err,**request.kwargs)
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=1),'Exception',str(type) + ':' + str(value))
    
    def _call_on_get_handler(self,request,element, env):
        
        if request.on_get is None: return
        self.partialresponseslock.acquire()
        if self.partialresponses.has_key(request.requestid):
            self.partialresponseslock.release()
            self._add_partialcontent(request, element, env)
            self.partialresponseslock.acquire()
            element = self.partialresponses[request.requestid]
            self.partialresponseslock.release()
        else:
            self.partialresponseslock.release()
        
        if element is None: return
        try:
            if not request.rawelement:
                element = self._objectmaker.make_object(element)
            if request.threaded:
                getthread = threading.Thread(target=request.on_get,arg=(env,request.path,element),kwargs=request.kwargs)
                getthread.start()
            else:
                request.on_get(env,request.path,element,**request.kwargs)
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=1),'Exception',str(type) + ':' + str(value))
    
    def _call_on_partial_handler(self,request,element, env):
        
        if request.on_partial is None or element is None: 
            print 'on_partial is None OR element is None'
            return
        try:
            if not request.rawelement:
                element = self._objectmaker.make_object(element)
            if request.threaded:
                partialthread = threading.Thread(target=request.on_partial,arg=(env,request.path,element),kwargs=request.kwargs)
                partialthread.start()
            else:
                request.on_partial(env,request.path,element,**request.kwargs)
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=1),'Exception',str(type) + ':' + str(value))
            
    def _call_on_add_handler(self,subscription,env,element):
        #TODO: rename 'subscription' to something less specific, but clearer ;-)
        if subscription.on_add is None: return
        try:
            if subscription.threaded:
                subthread = threading.Thread(target=subscription.on_add,args=(env,subscription.path,element),kwargs=subscription.kwargs)
                subthread.start()
            else:
                subscription.on_add(env,subscription.path,element,**subscription.kwargs)
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=1),'Exception',str(type) + ':' + str(value))
    
    def _call_on_delete_handler(self,subscription,env,key):

        if subscription.on_delete is None: return
        try:
            if subscription.threaded:
                subthread = threading.Thread(target=subscription.on_add,args=(env,subscription.path,key),kwargs=subscription.kwargs)
                subthread.start()
            else:
                subscription.on_delete(env,subscription.path,key,**subscription.kwargs)
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,'0.0.0.0','""','""',iologger.current_function(depth=1),'Exception',str(type) + ':' + str(value))
